package com.suzhiliang.springbootlesson.securityOAuth2;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.EnvironmentAware;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.core.env.Environment;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.oauth2.config.annotation.configurers.ClientDetailsServiceConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configuration.AuthorizationServerConfigurerAdapter;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableResourceServer;
import org.springframework.security.oauth2.config.annotation.web.configuration.ResourceServerConfigurerAdapter;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerEndpointsConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerSecurityConfigurer;
import org.springframework.security.oauth2.provider.token.DefaultTokenServices;
import org.springframework.security.oauth2.provider.token.TokenStore;
import org.springframework.security.oauth2.provider.token.store.JdbcTokenStore;

import javax.sql.DataSource;

/**
 * Copyright (C), 2015-2019, XXX有限公司
 *
 * @ClassName: OAuth2Configuration
 * @Author: xmm
 * @Date: 2019/7/23 9:57
 * @Description: OAuth2总配置类
 * @Version 1.0
 */
@Configuration
public class OAuth2Configuration {


    //继承自ResourceServerConfigurerAdapter完成资源服务器的配置
    @Configuration
    @EnableResourceServer//注解来开启资源服务器
    public static class ResourceServerConfiguration extends ResourceServerConfigurerAdapter{

        @Autowired
        private CustomAuthenticationEntryPoint customAuthenticationEntryPoint;

        @Autowired
        private CustomLogoutSuccessHandler customLogoutSuccessHandler;

        @Override
        public void configure(HttpSecurity http) throws Exception {
            http.exceptionHandling()
                    .authenticationEntryPoint(customAuthenticationEntryPoint)
                    .and()
                    .logout()
                    .logoutUrl("/oauth/logout")
                    .logoutSuccessHandler(customLogoutSuccessHandler)
                    .and()
                    .authorizeRequests()
                    .antMatchers("/hello","/login").permitAll()
                    .antMatchers("/secure/**").authenticated();
        }
    }

    //继承自ResourceServerConfigurerAdapter完成资源服务器的配置
    @Configuration
    @EnableResourceServer//注解来开启资源服务器
    public static class AuthorizationServerConfiguration
            extends AuthorizationServerConfigurerAdapter  {
        //从yml读取数据

        @Value("${authentication.oauth.clientid}")
        private String clientid;
        @Value("${authentication.oauth.secret}")
        private String secret;
        @Value("${authentication.oauth.tokenValiditySeconds}")
        private String tokenValiditySeconds;

        @Autowired
        private DataSource dataSource;

        @Autowired
        private SzlUserDetailsService szlUserDetailsService;

        @Autowired
        private AuthenticationManager authenticationManager;


        @Bean
        public TokenStore tokenStore(){
            return new JdbcTokenStore(dataSource);
        }

        @Bean
        @Primary
        public DefaultTokenServices tokenServices(){
            //TestDefaultTokenServices设置createToken同步操作
            DefaultTokenServices defaultTokenServices = new TestDefaultTokenServices();
            //是否支持刷新令牌。
            defaultTokenServices.setSupportRefreshToken(true);
            //是否重用刷新令牌（直到过期）
            defaultTokenServices.setReuseRefreshToken(false);
            //令牌存储的持久性策略
            defaultTokenServices.setTokenStore(tokenStore());
            //刷新令牌的有效性（秒）。
            defaultTokenServices.setAccessTokenValiditySeconds(1800);
            return defaultTokenServices;
        }

        @Override
        public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
            /**
             * 设置用户认证的管理器和生成的令牌的存储方式
             */
            endpoints.authenticationManager(this.authenticationManager)
                    //解决token并发问题
                     .tokenStore(tokenStore());
            /**
              *  必须设置UserDetailsService才能使用refresh_token：
              *  指定使用refresh_token换取access_token时，
              *  从哪里获取认证用户信息
              */
            endpoints.userDetailsService(szlUserDetailsService);
        }

        @Override
        public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
            /**
             * 获取令牌不需要认证，校验令牌需要认证，允许表单认证
             */
            // 开启/oauth/token_key验证端口无权限访问
            security.tokenKeyAccess("permitAll()")
                    // 开启/oauth/check_token验证端口认证权限访问
                    .checkTokenAccess("permitAll()")//isAuthenticated() :校验令牌需要认证 , permitAll() :校验令牌不需要认证
                    .allowFormAuthenticationForClients(); //允许表单认证
        }

        /**
         * 颁发令牌的客户端配置
         */
        @Override
        public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
            clients.inMemory()
                    //用于标识用户ID
                    .withClient(clientid)
                    //授权范围
                    .scopes("all")
                    .authorities(Authorities.ROLE_ADMIN.name(),Authorities.ROLE_USER.name())
                    /**
                     * 授权方式
                     * authorization_code 授权码授权方式获取令牌
                     * refresh_token 刷新令牌方式获取令牌
                     * password 密码授权方式获取令牌
                     * implicit 简化授权方式获取令牌
                     * client_credentials 客户端授权方式获取令牌
                     */
                    .authorizedGrantTypes("password","refresh_token")
                    .secret(secret)
                    //授权码存活时间
                    .accessTokenValiditySeconds(Integer.parseInt(tokenValiditySeconds));
        }

    }
}
